/**
 * 保护对象
 */

var {log} = console;

/**
 * 
 *  1. 保护对象的属性
        es5对对象的属性进行了重新的分类
        1.1 命名属性: 可用.访问到的属性
                数据属性：实际存储属性值的属性
                访问器属性：不实际存储属性值的属性，仅提供对一个数据属性的保护
        1.2 内部属性: 无法用.访问到的属性
        1.3 如何保护数据属性
            a. 旧的js中，每个属性值都是一个普通的变量
            b. es5标准中每个数据属性都是一个缩微的小对象，每个小对象中保存了一个属性值和三个开关
                以var eric = {
                    eid: 1000,
                    ename: "奥特曼"
                }为例，在es5标准中，eid变量将被转换为一个对象，其展开形式为
                eid: {
                    value: 1000,
                    writable: true/false,
                    enumerable: true/false,
                    configurable: true/false
                }, 其中value表示实际存储的属性值
                    writable：是否可被修改
                    enumerable：是否可被遍历(for in)
                    configurable：是否可被配置：是否可删除该属性、是否可删除前两个开关

            c. *** 使用专门的函数修改变量的属性：Object.defineProperty(
                对象名,
                "属性名",
                {
                    开关: true/false
                }
            )
                *** 但是设置开关后，仅仅设置writable和enumerable不能强制关闭开关(任何人都能随意开启)
                *** 则需要在第一次设置开关时需要将configurable同时关闭，一旦改为false则不可逆
                Object.defineProperty()一次只能修改对象的一个属性，使用Object.defineProperties可以同时修改
                Object.defineProperties(
                    eric,
                    {
                        eid: {
                            writable: false, // 禁止修改
                            configurable: false
                        },
                        ename: {
                            enumerable: false // 禁止遍历，仍然可以使用.获取属性值，但是防住了for in遍历
                        },
                        eclass: {
                            configurable: false // 禁止删除
                        }
                    }
                )
            d. *** 自定义规则保护属性
                a. 举例
                    以var eric = {
                        eid: 1000,
                        eage: 25,
                        ename: "奥特曼",
                        eclass: "光之国"
                    };为例，如果想对eage进行修改，但是必须保证在18-65之间，这个时候writable是不可行的
                b. 使用访问器属性来保护数据属性
                    什么是访问器属性：自己不保存数据，专门提供对其他数据的保护
                    如何做呢？
                        1. 将变量名改为：——eage
                        2. 将要保护的数据半隐藏，即修改enumerable配置
                        3. 使用property "保镖"的get和set特性: 见124-150行左右代码块
    2. 保护对象的结构
        3个结构
        2.1 防止拓展：禁止添加新属性
            Object.preventExtensions(eric)
            eric.eheight = 182
            log(eric) // { eid: 1000, eclass: '光之国' }
            原理：每个对象内部都存在一个隐藏的extensiable属性，默认值为true
                 preventExtensions就是就是将这个属性改为false
        2.2 密封：在防止拓展的基础上进一步禁止删除
            Object.seal(eric)
            eric.eheight = 182
            delete eric.ename
            log(eric) // { eid: 1000, eclass: '光之国' }
            原理：将对象内部所有变量属性的configurable属性改为false
        2.3 冻结：
            Object.freeze(eric)
            eric.eheight = 182
            delete eric.ename
            log(eric) // { eid: 1000, eclass: '光之国' }
 */



// 1. 如何保护数据属性
// 1.1 使用Object.defineProperty强制保护
var eric = {
    eid: 1000,
    _eage: 25,
    ename: "奥特曼",
    eclass: "光之国"
};

var eid = {
    value: 1000,
    writable: true/false,
    enumerable: true/false,
    configurable: true/false
};

// Object.defineProperty 单次修改 对象的属性配置
Object.defineProperty(
    eric,
    "eid",
    {
        writable: false,
        configurable: false
    }
);
eric.eid = 1001;
// 可以发现通过保护eric的属性值后, eid的值没有发生改变(configurable关闭，一旦改为false则不可逆)
log(eric) // { eid: 1000, ename: '奥特曼' }

// Object.defineProperties 同时修改 对象的属性配置
Object.defineProperties(
    eric,
    {
        eid: {
            writable: false, // 禁止修改
            configurable: false
        },
        ename: {
            enumerable: false // 禁止遍历
        },
        eclass: {
            configurable: false // 禁止删除
        }
    }
)
log(eric) // { eid: 1000, eclass: '光之国' }

// 1.2 property自定义保护属性
Object.defineProperties(
    eric,
    {
        _eage: {
            enumerable: false, // 禁止遍历, 半隐藏
            configurable: false // 禁止删除
        },
        eage: {
            get: function() {
                return this._eage
            },
            set: function(NewValue) {
                if (NewValue > 18 && NewValue < 65) {
                    this._eage = NewValue
                } else {
                    throw Error('age must between 18 and 65')
                }
            }
        }
    }
)
log(eric.eage) // 25
eric.eage = 38
log(eric.eage) // 38
// eric.eage = 80
// log(eric.eage) // age must between 18 and 65


// 2. 保护对象的结构
// 2.1 禁止添加新属性
Object.preventExtensions(eric)
eric.eheight = 182
log(eric) // { eid: 1000, eclass: '光之国' }
// 2.2 密封：在防止拓展的基础上进一步禁止删除
Object.seal(eric)
eric.eheight = 182
delete eric.ename
log(eric) // { eid: 1000, eclass: '光之国' }
// 2.3
Object.freeze(eric)
eric.eheight = 182
delete eric.ename
log(eric) // { eid: 1000, eclass: '光之国' }
